<?php
/**
 * 微信支付
 */

namespace utils;


use app\Request;
use exceptions\ApiException;
use think\facade\Db;
use WeChatPay\Builder;
use WeChatPay\Util\PemUtil;

class WxPay
{
    protected static $instance = null;
    protected $conf;

    public $order_state = !1;

    public function __construct ($type = 'pay')
    {
        $this->conf = Db::name('pay_config')->field('serial_no,mchid,secret,client_cert,client_key,wx_appid_app')->where(['channel' => 2, 'state' => 1])->findOrFail();
        if ($type == 'pay') {
            $platformCertificateFilePath = 'file://' . $this->conf['client_cert']; // 平台证书
            $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
            $platformPublicKeyInstance = PemUtil::loadCertificate($platformCertificateFilePath);
            self::$instance = Builder::factory([
                // 商户号
                'mchid' => $this->conf['mchid'],
                // 商户证书序列号
                'serial' => $this->conf['serial_no'],
                // 商户API私钥 PEM格式的文本字符串或者文件resource
                'privateKey' => PemUtil::loadPrivateKey('file://' . $this->conf['client_key']),
                'certs' => [
                    // 可由内置的平台证书下载器 `./bin/CertificateDownloader.php` 生成
                    $platformCertificateSerial => $platformPublicKeyInstance
                ],
            ]);
        }
    }


    /**
     * 微信下单
     * @param array $param
     * @return array
     */
    public function unifiedOrder (array $param)
    {
        try {
            $resp = self::$instance->chain('v3/pay/transactions/app')->post(['json' => [
                'mchid' => $this->conf['mchid'],
                'out_trade_no' => "{$param['out_trade_no']}",
                'appid' => $this->conf['wx_appid_app'],
                'description' => "{$param['body']}",
                'notify_url' => get_config(1, 'API_URL') . 'wxResult', // 支付通知地址,
                'amount' => [
                    'total' => (int)$param['total_amount'],
                    'currency' => 'CNY'
                ],
            ]]);
            $rpm = json_decode($resp->getBody(), true);
            $sign_arr = [
                'appId' => $this->conf['wx_appid_app'],
                'timeStamp' => time(),
                'nonceStr' => nonce_str(),
                'prepayid' => $rpm['prepay_id']
            ];
            $sign_arr['sign'] = $this->createApiSign($sign_arr);
            $sign_arr['package'] = 'Sign=WXPay';
            $sign_arr['partnerid'] = $this->conf['mchid'];
            return ['flag' => true, 'msg' => '调用成功', 'data' => $sign_arr];
        } catch (\Throwable $e) {
            return ['flag' => false, 'msg' => "调用失败，" . $e->getMessage() . PHP_EOL];
        }
    }

    /**
     * 交易订单查询
     * @param string $out_trade_no
     * @return bool
     */
    public function tradeQuery (string $out_trade_no)
    {
        $action = 'out-trade-no';
        self::$instance->v3->pay->transactions->{$action}->{"{$out_trade_no}"}
            ->getAsync([
                // 查询参数结构
                'query' => ['mchid' => $this->conf['mchid']],
                // uri_template 字面量参数
                'out_trade_no' => $out_trade_no,
            ])
            ->then(static function ($response) {
                // 正常逻辑回调处理
                $result = json_decode($response->getBody(), true);
                if ($result['trade_state'] == 'SUCCESS') {
                    Db::name('payment')->where('out_trade_no', $result['out_trade_no'])->update(['state' => 6, 'pay_at' => date('Y-m-d H:i:s'), 'trade_no' => $result['transaction_id']]); // 更改订单状态
                }
            })
            ->otherwise(static function ($e) {
            })
            ->wait();
        $order_state = Db::name('payment')->where('out_trade_no', $out_trade_no)->value('state');
        $order_state == 6 && $this->order_state = !0;
        return $this->order_state;
    }

    /**
     * 交易订单查询（线下）
     * @param string $transaction_id
     * @return bool
     */
    public function tradeQueryOffline (string $transaction_id)
    {
        $action = 'id';
        self::$instance->v3->pay->transactions->{$action}->{"{$transaction_id}"}
            ->getAsync([
                // 查询参数结构
                'query' => ['mchid' => $this->conf['mchid']],
                // uri_template 字面量参数
                'transaction_id' => $transaction_id,
            ])
            ->then(static function ($response) {
                // 正常逻辑回调处理
                $result = json_decode($response->getBody(), true);
                if ($result['trade_state'] == 'SUCCESS') {
                    $obj = Db::name('payment')->where('trade_no', $result['transaction_id']);
                    $payment_info = $obj->findOrFail();
                    $total_amount = $result['amount']['total'] / 100;
                    if ((int)$total_amount != (int)$payment_info['total_fee']) {
                        $obj->save(['remarks' => '金额不符']); // 更改订单状态
                    } else {
                        $obj->save(['state' => 6, 'pay_at' => date('Y-m-d H:i:s'), 'out_trade_no' => $result['out_trade_no'], 'mchid' => $result['mchid']]); // 更改订单状态
                    }
                }
            })
            ->otherwise(static function ($e) {
            })
            ->wait();
        $order_state = Db::name('payment')->where('trade_no', $transaction_id)->field('state,remarks')->findOrEmpty();
        if ($order_state['remarks'] == '金额不符')
            throw new ApiException('金额不符');
        $order_state['state'] == 6 && $this->order_state = !0;
        return $this->order_state;
    }

    /**
     * 获取接口签名
     * @param array $params
     * @return string
     */
    protected function createApiSign (array $params): string
    {
        //签名步骤一：按字典序排序数组参数
        ksort($params);
        $string = $this->toUrlParams($params);
        //签名步骤二：在string后加入KEY
        $app_key = $this->conf['secret'];
        $string = trim($string . "&key=" . $app_key);
        //签名步骤三：MD5加密
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 将参数拼接为url: key=value&key=value
     * @param  $params
     * @return string
     */
    protected function toUrlParams ($params)
    {
        $string = '';
        if (!empty($params)) {
            $array = array();
            foreach ($params as $key => $value) {
                $array[] = $key . '=' . $value;
            }
            $string = implode("&", $array);
        }
        return $string;
    }

    /**
     * 通知数据解密
     * @param $param
     * @return mixed|string
     */
    public function notifyDecode ($param)
    {
        $nonce = $param['nonce'];
        $associated_data = $param['associated_data'];
        $ciphertext = $param['ciphertext'];
        $ciphertext = base64_decode($ciphertext);
        $order_data = \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associated_data, $nonce, $this->conf['secret']);
        $order_data = json_decode($order_data, true);
        return $order_data;
    }

    /**
     * 验证签名
     */
    public function checkSign ()
    {
        $obj = new Request();
    }
}